﻿using Chromium;
using Chromium.Remote;
using Chromium.Remote.Event;
using Chromium.WebBrowser;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace M5.GeminiHybird.Core.JavascriptEvent
{
    public class BaseJavescriptEvent
    {
        internal Form CurrExcuteFrom { get; set; }
        internal Dictionary<string, string> CSharpAndJsFunNamesPairs { get; set; } = new Dictionary<string, string>();

        private CfrV8Value ReturnPropertysBuilder(object propertyValue)
        {
            CfrV8Value v8Value = null;
            Type propertyValueType = propertyValue.GetType();

            if (propertyValue is string || propertyValue is long || propertyValue is ulong)
            {
                v8Value = CfrV8Value.CreateString(Convert.ToString(propertyValue));
            }
            else if (propertyValue is int || propertyValue is byte || propertyValue is short || propertyValue is uint || propertyValue is ushort)
            {
                v8Value = CfrV8Value.CreateInt(Convert.ToInt32(propertyValue));
            }
            else if (propertyValue is DateTime)
            {
                var time = Convert.ToDateTime(propertyValue);
                v8Value = CfrV8Value.CreateString(time.ToString("yyyy-MM-dd HH:mm:ss"));
            }
            else if (propertyValue is decimal || propertyValue is float || propertyValue is double)
            {
                v8Value = CfrV8Value.CreateDouble(Convert.ToDouble(propertyValue));
            }
            else if (propertyValue is bool)
            {
                v8Value = CfrV8Value.CreateBool(Convert.ToBoolean(propertyValue));
            }
            else if (propertyValueType.IsGenericType && propertyValueType.GetGenericTypeDefinition() == typeof(List<>))
            {
                var enumerable = propertyValue as System.Collections.IEnumerable;
                var list = enumerable.OfType<object>().ToList();
                var v8Array = CfrV8Value.CreateArray(list.Count);
                int i = 0;
                foreach (var listItem in enumerable)
                {
                    if (listItem.GetType().IsClass && !(listItem is string))
                    {
                        v8Array.SetValue(i, ReturnObjectBuilder(listItem));
                    }
                    else
                    {
                        v8Array.SetValue(i, ReturnPropertysBuilder(listItem));
                    }
                    i++;
                }

                v8Value = v8Array;
            }
            else if (propertyValueType.IsClass)
            {
                v8Value = ReturnObjectBuilder(propertyValue);
            }
            else if (propertyValueType.IsEnum)
            {
                v8Value = CfrV8Value.CreateInt((int)propertyValue);
            }
            else
            {
                v8Value = CfrV8Value.CreateString(Convert.ToString(propertyValue));
            }

            return v8Value;
        }

        private CfrV8Value ReturnObjectBuilder(object returnData)
        {
            var jsObjectAccessor = new CfrV8Accessor();
            var jsObject = CfrV8Value.CreateObject(jsObjectAccessor);


            Type t = returnData.GetType();

            if (t.IsClass && !(returnData is string))
            {
                var propertys = t.GetProperties();
                foreach (var item in propertys)
                {
                    object v = item.GetValue(returnData, null);

                    CfrV8Value itemValue = ReturnPropertysBuilder(v);

                    jsObject.SetValue(item.Name, itemValue, CfxV8PropertyAttribute.None);
                }
            }
            else
            {
                CfrV8Value itemValue = ReturnPropertysBuilder(returnData);
                return itemValue;
            }

            return jsObject;
        }

        internal CfrV8Value ReturnValueBuilder(object model)
        {
            return ReturnObjectBuilder(model);
        }

        private object InvokeParameterBuilder(CfrV8Value v8Value, Type parameterType, out Type valueType)
        {
            valueType = null;
            object returnValue = DefaultForType(parameterType);

            if (v8Value.IsInt)
            {
                returnValue = v8Value.IntValue;
            }
            else if (v8Value.IsString)
            {
                returnValue = v8Value.StringValue;
                if (parameterType == typeof(DateTime))
                {
                    returnValue = Convert.ToDateTime(returnValue);
                }
            }
            else if (v8Value.IsDate)
            {
                var v8Date = v8Value.DateValue;
                var utcTime = v8Date.ToUniversalTime(v8Date);
                returnValue = utcTime.ToLocalTime();
            }
            else if (v8Value.IsDouble)
            {
                returnValue = v8Value.DoubleValue;

                if (parameterType == typeof(long))
                {
                    BitConverter.GetBytes((double)returnValue);
                }
            }
            else if (v8Value.IsBool)
            {
                returnValue = v8Value.BoolValue;
            }
            else if (v8Value.IsArray)
            {
                if (parameterType.IsArray)
                {
                    var arrayItemType = parameterType.GetElementType();
                    Array array = Array.CreateInstance(arrayItemType, v8Value.ArrayLength);
                    for (int i = 0; i < v8Value.ArrayLength; i++)
                    {
                        array.SetValue(InvokeParameterBuilder(v8Value.GetValue(i), arrayItemType, out Type t), i);
                    }
                    returnValue = array;
                }
                else if (parameterType.IsGenericType)
                {
                    var listItemTypes = parameterType.GetGenericArguments();
                    Type type = listItemTypes[0];
                    Type generic = typeof(List<>);
                    generic = generic.MakeGenericType(new Type[] { type });
                    var list = Activator.CreateInstance(generic) as System.Collections.IList;
                    for (int i = 0; i < v8Value.ArrayLength; i++)
                    {
                        list.Add(InvokeParameterBuilder(v8Value.GetValue(i), listItemTypes[0], out Type tt));
                    }
                    returnValue = list;
                }

            }
            else if (v8Value.IsObject)
            {
                object instanceObject = parameterType.Assembly.CreateInstance(parameterType.FullName);

                foreach (var param in parameterType.GetProperties())
                {
                    var v8ParamValue = v8Value.GetValue(param.Name);

                    param.SetValue(instanceObject, InvokeParameterBuilder(v8ParamValue, param.PropertyType, out Type t), null);

                }
                returnValue = instanceObject;
            }

            if (returnValue == null)
                valueType = parameterType;
            else
                valueType = returnValue.GetType();

            return returnValue;
        }

        private void FindExcuteForm(Control control)
        {
            if (control == null)
                return;

            if (control is Form)
                CurrExcuteFrom = control as Form;
            else
                FindExcuteForm(control.Parent);
        }

        internal void CommonJavascript_Filter(object sender, Chromium.Remote.Event.CfrV8HandlerExecuteEventArgs e)
        {
            var jsFunction = (sender as Chromium.WebBrowser.JSFunction);

            //List<string> parentPath = new List<string>() { jsFunction.Name};
            //var a = jsFunction.Browser.Parent is System.Windows.Forms.Form;
            //var b = jsFunction.Browser.Parent.Parent is System.Windows.Forms.Form;

            if (jsFunction.Browser.Parent != null)
            {
                FindExcuteForm(jsFunction.Browser.Parent);
            }

            //if(jsFunction.Parent.Name == "runApp")
            //{
            //    new Thread(() => {
            //        var path = @"E:\Works\凭证查询\Voucher\bin\Debug\CheckVoucher.exe";



            //        ProcessStartInfo psi = new ProcessStartInfo();
            //        psi.FileName = path;
            //        psi.LoadUserProfile = true;
            //        psi.WorkingDirectory = Path.GetDirectoryName(path);
            //        psi.WindowStyle = ProcessWindowStyle.Normal;

            //        var p = Process.Start(psi);
            //        p.WaitForInputIdle();


            //        Thread.Sleep(1000);

            //        string script = "{0}{{TAB}}{1}";
            //        script = string.Format(script, "troy", "123");
            //        SendKeys.SendWait(script);

            //    }).Start();


            //    return;
            //}


            //获取执行类
            var invokeObject = this;
            //查找执行方法
            var invokeMethod = invokeObject.GetType().GetMethod(this.CSharpAndJsFunNamesPairs[jsFunction.Parent.Name + "." + jsFunction.Name]);
            if (invokeMethod == null)
            {
                throw new MissingMethodException($"在'{invokeObject.GetType().FullName}'中没有找到名为 'Index' 的方法.");
            }

            //获取执行方法的参数列表
            var invokeMethodParameters = invokeMethod.GetParameters();

            List<object> invokeParam = new List<object>();


            //调用参数
            var inputArguments = e.Arguments;

            for (int i = 0; i < inputArguments.Length; i++)
            {
                var v = inputArguments[i];

                invokeParam.Add(InvokeParameterBuilder(v, invokeMethodParameters[i].ParameterType, out Type t));
            }

            try
            {
                object returnObj = invokeMethod.Invoke(invokeObject, invokeParam.ToArray());

                if (returnObj == null)
                    return;

                var v8ReturnValue = ReturnValueBuilder(returnObj);

                e.SetReturnValue(v8ReturnValue);

            }
            catch (Exception ex)
            {

                //e.Exception = ex.ToString();
                Console.WriteLine(ex.ToString());
            }


        }


        private object DefaultForType(Type targetType)
        {
            return targetType.IsValueType ? Activator.CreateInstance(targetType) : null;
        }
    }
}
